package edu.umn.cs5115.scheduler;

import edu.umn.cs5115.scheduler.entities.Course;
import edu.umn.cs5115.scheduler.entities.MeetingTime;
import edu.umn.cs5115.scheduler.entities.Section;
import edu.umn.cs5115.scheduler.entities.CourseFactory;
import edu.umn.cs5115.scheduler.entities.Weekday;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Hashtable;
import java.util.Enumeration;
import java.io.UnsupportedEncodingException;

import org.w3c.tidy.Tidy;
import java.net.URL;
import java.net.URLEncoder;
import javax.xml.xpath.*;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import java.util.regex.*;

public class WebInterface {
	private static WebInterface instance = null;
	
	private static final XPathFactory xPathFactory;
	private static final XPath xpath;
	
	public static Hashtable<String, String> sessionCodes;
	public static Hashtable<String, String> libEdCodes;
	public static Hashtable<String, String> campusAreaCodes;
	public static Hashtable<String, String> humanSearchKeys;
	public Hashtable<String, String> academicSessionCodes;
	private static SimpleDateFormat dateFormatter;
	private static SimpleDateFormat timeFormatter;
	
	static {
		xPathFactory = XPathFactory.newInstance();
		xpath = xPathFactory.newXPath();
		
		sessionCodes = new Hashtable<String, String>();
		sessionCodes.put("all sessions", "ALL,ALL");
		sessionCodes.put("regular academic section", "001,Regular Academic Section");
		sessionCodes.put("first half of term", "009,First Half of Term");
		sessionCodes.put("second half of term", "010,Second Half of Term");
		
		libEdCodes = new Hashtable<String, String>();
		libEdCodes.put("biological science with laboratory core", "CLE,BIOL SCI/L,x,true");
		libEdCodes.put("citizenship and public ethics theme", "CLE,C/PE,x,true");
		libEdCodes.put("cultural diversity theme", "CLE,CD,x,true");
		libEdCodes.put("environment theme", "CLE,ENVT,x,true");
		libEdCodes.put("historical perspective core", "CLE,HP,x,true");
		libEdCodes.put("international perspective theme", "CLE,IP,x,true");
		libEdCodes.put("literature core", "CLE,CLE,LIT,x,true");
		libEdCodes.put("mathematical thinking core", "CLE,MATH,x,true");
		libEdCodes.put("other humanities core", "CLE,OH,x,true");
		libEdCodes.put("physical science with laboratory core", "CLE,PHYS SCI/L,x,true");
		libEdCodes.put("social science core", "CLE,SSCI,x,true");
		libEdCodes.put("writing intensive", "CLE,WI,x,true");
		
		campusAreaCodes = new Hashtable<String, String>();
		campusAreaCodes.put("east bank", "TCEASTBANK");
		campusAreaCodes.put("west bank", "TCWESTBANK");
		campusAreaCodes.put("st. paul", "STPAUL");
		campusAreaCodes.put("rochester", "ROCHESTER");
		campusAreaCodes.put("off campus tc", "OFFCMPTC");
		campusAreaCodes.put("off campus rochester", "OFFCMPROCH");
		
		dateFormatter = new SimpleDateFormat("MM/dd/yyyy");
		timeFormatter = new SimpleDateFormat("hh:mm a");
		
		humanSearchKeys = new Hashtable<String, String>();
		humanSearchKeys.put("searchSubjects", "Subject");
		humanSearchKeys.put("searchCatalogNumber", "Course Number");
		humanSearchKeys.put("searchInstructorName", "Instructor Last Name");
		humanSearchKeys.put("searchLowerStartTime", "Time Range");
		humanSearchKeys.put("searchLowerLevelLimit", "Level Range");
		humanSearchKeys.put("searchLowerCreditLimit", "Credit Range");
		humanSearchKeys.put("searchCourseTitle", "Class Title/Key Words");
		humanSearchKeys.put("searchCourseAttributes", "Lib Ed Reqs");
		humanSearchKeys.put("searchSessionCodes", "Sessions");
		humanSearchKeys.put("searchLocations", "Campus Areas");
		humanSearchKeys.put("searchMon", "Monday");
		humanSearchKeys.put("searchTue", "Tuesday");
		humanSearchKeys.put("searchWed", "Wednesday");
		humanSearchKeys.put("searchThu", "Thursday");
		humanSearchKeys.put("searchFri", "Friday");
		humanSearchKeys.put("searchSat", "Saturday");
		humanSearchKeys.put("searchSun", "Sunday");
	}
	
	private static Tidy tidy;
	private ArrayList<String> sessions;
	private ArrayList<String> departments;
	
	
	
	// for testing purposes.
	public static void main(String args[]) {
		WebInterface rampage = new WebInterface();
		SearchQuery query = new SearchQuery();
		query.addSubject("CSCI");
		query.searchCourseNumber("3081W");
		SchedulerDocument document = new SchedulerDocument(); 
		rampage.search(query, document);
	}
	
	public static WebInterface getWebInterface() {
		if(instance == null)
			instance = new WebInterface();
		
		return instance;
	}
	
	protected WebInterface() {
		tidy = new Tidy(); 
		tidy.setQuiet(true);
		tidy.setShowWarnings(false);
		
		
		// for our academic session codes to search properly.  This hash is populated
		// when the user retrieves the list of sessions.
		
		academicSessionCodes = new Hashtable<String, String>();
	}
	
	
	/*
	 * getSessions returns the current sessions listed on the website.  Using this
	 * method will allow us to not make new releases each semester!
	 * 
	 * Returns an array of strings representing the name of each session.
	 */
	public ArrayList<String> getSessions() {
		if(sessions == null || sessions.size() == 0) {
			sessions = new ArrayList<String>();
		    try {
		    	XPathExpression expr = xpath.compile("//select[@name='searchTerm']/option");
		    	
				URL webpage = new URL("http://onestop2.umn.edu/courseinfo/searchcriteria.jsp?institution=UMNTC");
				Document doc = tidy.parseDOM(webpage.openStream(), null);
			    Object result = expr.evaluate(doc, XPathConstants.NODESET);
			    NodeList nodes = (NodeList) result;
			    for (int i = 0; i < nodes.getLength(); i++) {
			    	if(!nodes.item(i).getFirstChild().getNodeValue().startsWith("Select")) {
			    		
			    		String name = nodes.item(i).getFirstChild().getNodeValue();
			    		String value = nodes.item(i).getAttributes().getNamedItem("value").getNodeValue();
			    		
			    		academicSessionCodes.put(name.toLowerCase(), value);
			    		
			    		sessions.add(name);
			    	}
			    }
			    
			}
			catch(Exception e) {
				e.printStackTrace();
				// nothing, damnit.  NOTHING.
			}
		}
		
		return sessions;
	}
	

	public ArrayList<String> getDepartments() {
		if(departments == null || departments.size() == 0) {
			XPath xpath = xPathFactory.newXPath();
			departments = new ArrayList<String>();
		    try {
		    	XPathExpression expr = xpath.compile("//select[@name='searchSubjects']/option");
		    	
				URL webpage = new URL("http://onestop2.umn.edu/courseinfo/searchcriteria.jsp?institution=UMNTC");
				Document doc = tidy.parseDOM(webpage.openStream(), null);
			    Object result = expr.evaluate(doc, XPathConstants.NODESET);
			    NodeList nodes = (NodeList) result;
			    for (int i = 0; i < nodes.getLength(); i++) {
			    	if(!nodes.item(i).getFirstChild().getNodeValue().startsWith("All Subjects")) {
			    		String name = nodes.item(i).getFirstChild().getNodeValue();
			    		departments.add(name);
			    	}
			    }
			    
			}
			catch(Exception e) {
				e.printStackTrace();
			}
		}
		
		return departments;
	}

	/* 
	 * Executes the query on onestop, parses the result, and returns an array of classes.
	 */
	public void search(SearchQuery query, SchedulerDocument document) {
		// Apply some defaults.
		applyDefaults(query);
		
		SearchThread searcher = new SearchThread();
                
                // makes it so that the application quits immediately if the 
                // user quits while a search is in progress, rather than waiting
                // for the search to finish.
                searcher.setDaemon(true);
                
                query.setSearch(searcher);
		searcher.setSearchQuery(query);
		searcher.setDocument(document);
		searcher.start();
	}

	
	/*
	 * Applies the default search criteria, which allows for searching while
	 * only specifying a few parameters.  The web interface, however, requires
	 * the following in order to search.
	 */
	private void applyDefaults(SearchQuery query) {
		Hashtable <String, Object> currentParams = query.getParams();
		
		// search day defaults to searching all days.
		if(!(currentParams.containsKey("searchMon") || currentParams.containsKey("searchTue")
				|| currentParams.containsKey("searchWed") || currentParams.containsKey("searchThu")
				||currentParams.containsKey("searchFri") || currentParams.containsKey("searchSat")
				|| currentParams.containsKey("searchSun")))
		{
			query.searchDay("Mon", true);
			query.searchDay("Tue", true);
			query.searchDay("Wed", true);
			query.searchDay("Thu", true);
			query.searchDay("Fri", true);
			query.searchDay("Sat", true);
			query.searchDay("Sun", true);
		}
		
		// searchStartTime defaults to 8:00 am
		if(!currentParams.containsKey("searchLowerStartTime")) {
			query.searchStartTime("08:00");
		}
		
		// searchEndTime defaults to 11:59 pm
		if(!currentParams.containsKey("searchUpperEndTime")) {
			query.searchEndTime("23:59");
		}
		
		// Level Defaults
		if(!currentParams.containsKey("searchLowerLevelLimit")) {
			query.searchLowerLevelLimit("0");
		}
		
		if(!currentParams.containsKey("searchUpperLevelLimit")) {
			query.searchUpperLevelLimit("9xxx");
		}
		
		// Credit Defaults
		if(!currentParams.containsKey("searchLowerCreditLimit")) {
			query.searchLowerCreditLimit("0");
		}
		
		if(!currentParams.containsKey("searchUpperCreditLimit")) {
			query.searchUpperCreditLimit("9999");
		}
		
		// Session default
		if(!currentParams.containsKey("searchSessionCodes")) {
			query.addSession("all sessions");
		}
		
		// Campus Area defaults
		if(!currentParams.containsKey("searchLocations")) {
			query.addCampusArea("east bank");
			query.addCampusArea("west bank");
			query.addCampusArea("st. paul");
			query.addCampusArea("rochester");
			query.addCampusArea("off campus tc");
			query.addCampusArea("off campus rochester");
		}
		
		// set the default search term to the first session...  not very good
		// but this should be set outside of the class.
		
		if(!currentParams.containsKey("searchTerm")) {
			if(sessions == null || sessions.size() == 0)
				getSessions();
			
			query.searchTerm(sessions.get(2));
		}
	}
	
	public Hashtable<String, String> getAcademicSessionCodeHash() {
		if(academicSessionCodes.size() == 0) {
			getSessions();
		}
		
		return academicSessionCodes;
	}
}
